﻿/************************************************************************

 This file is part of Chroma.

 EscapeFromPrisonPlanet is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 EscapeFromPrisonPlanet is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with Foobar.  If not, see <http://www.gnu.org/licenses/>.

************************************************************************/

// All code (c)2013 Scott Bevin. all rights reserved

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace LudumDare26.PrimitiveRendering
{
	public class PrimitiveBatch
	{
		public Matrix Projection { get; set; }

		public Matrix View { get; set; }

		private List<PEPrimitive> opaquePrimitives;
		private List<PEPrimitive> transparentPrimitives;
		private GraphicsDevice graphicsDevice;
		private BasicEffect effect;

		private VertexPositionColorTexture[] vertices;
		private int vertexCount;

		private short[] indices;
		private int indiceCount;

		private int primitiveCount;
		private bool beginCalled;

		private int totalTriangleCount;
		private int totalVertexCount;
		private int totalDrawCalls;
		private int totalOpaquePrimitivesRendered;
		private int totalTransparentPrimitivesRendered;

		public int TriangleCount { get { return totalTriangleCount; } }

		public int VertexCount { get { return totalVertexCount; } }

		public int DrawCalls { get { return totalDrawCalls; } }

		public int OpaquePrimitiveCount { get { return totalOpaquePrimitivesRendered; } }

		public int TransparentPrimitiveCount { get { return totalTransparentPrimitivesRendered; } }

		public PrimitiveBatch(GraphicsDevice gd)
		{
			graphicsDevice = gd;

			effect = new BasicEffect(graphicsDevice);
			effect.VertexColorEnabled = true;

			opaquePrimitives = new List<PEPrimitive>();
			transparentPrimitives = new List<PEPrimitive>();

			vertices = new VertexPositionColorTexture[33000];
			indices = new short[11000];
		}

		public void Begin()
		{
			Begin(Matrix.CreateOrthographicOffCenter(0, graphicsDevice.Viewport.Width,
					graphicsDevice.Viewport.Height, 0, 0, 1), Matrix.Identity);
		}

		public void Begin(Matrix viewMatrix)
		{
			Begin(Matrix.CreateOrthographicOffCenter(0, graphicsDevice.Viewport.Width,
			  graphicsDevice.Viewport.Height, 0, 0, 1), viewMatrix);
		}

		public void Begin(Matrix projectionMatrix, Matrix viewMatrix)
		{
			Projection = projectionMatrix;
			View = viewMatrix;

			opaquePrimitives.Clear();
			transparentPrimitives.Clear();
			vertexCount = 0;
			indiceCount = 0;
			primitiveCount = 0;
			totalTriangleCount = 0;
			totalVertexCount = 0;
			totalDrawCalls = 0;
			totalOpaquePrimitivesRendered = 0;
			totalTransparentPrimitivesRendered = 0;
			beginCalled = true;
		}

		public void End()
		{
			if (!beginCalled)
				throw new Exception("You must call Begin() before End()");

			effect.World = Matrix.Identity;
			effect.View = View;
			effect.Projection = Projection;

			// traverse opaque primitive list...no sorting needed as depth buffer is used
			for (int i = 0; i < opaquePrimitives.Count; i++)
			{
				opaquePrimitives[i].Draw(ref vertices, ref vertexCount, ref indices, ref indiceCount);
				primitiveCount += opaquePrimitives[i].PrimitiveCount;

				if (primitiveCount > 3000)
					RenderOpaque();
			}

			RenderOpaque();

			// current method requires transparent primitives to be sorted prior to building
			// the vertice rendering list
			//transparentPrimitives.Sort();

			// traverse transparent primitive list and add values
			for (int i = 0; i < transparentPrimitives.Count; i++)
			{
				transparentPrimitives[i].Draw(ref vertices, ref vertexCount, ref indices, ref indiceCount);
				primitiveCount += transparentPrimitives[i].PrimitiveCount;

				if (primitiveCount > 3000)
					RenderTransparent();
			}

			RenderTransparent();

			totalOpaquePrimitivesRendered = opaquePrimitives.Count;
			totalTransparentPrimitivesRendered = transparentPrimitives.Count;

			beginCalled = false;
		}

		internal void RenderTransparent()
		{
			// don't render anything if nothing has been added to the list
			if (primitiveCount > 0)
			{
				// we are rendering transparent primitives so we do not write to the depth buffer
				graphicsDevice.BlendState = BlendState.NonPremultiplied;

				foreach (EffectPass pass in effect.CurrentTechnique.Passes)
				{
					pass.Apply();

					totalDrawCalls++;
					graphicsDevice.DrawUserIndexedPrimitives<VertexPositionColorTexture>(PrimitiveType.TriangleList, vertices, 0, vertexCount, indices, 0, primitiveCount);
				}
			}

			totalVertexCount += vertexCount;
			vertexCount = 0;
			indiceCount = 0;
			totalTriangleCount += primitiveCount;
			primitiveCount = 0;
		}

		internal void RenderOpaque()
		{
			graphicsDevice.RasterizerState = RasterizerState.CullNone;
			graphicsDevice.DepthStencilState = DepthStencilState.Default;
			graphicsDevice.BlendState = BlendState.Opaque;

			// don't render anything if nothing has been added to the list
			if (primitiveCount > 0)
			{
				foreach (EffectPass pass in effect.CurrentTechnique.Passes)
				{
					pass.Apply();

					totalDrawCalls++;
					graphicsDevice.DrawUserIndexedPrimitives<VertexPositionColorTexture>(PrimitiveType.TriangleList, vertices, 0, vertexCount, indices, 0, primitiveCount);
				}
			}

			totalVertexCount += vertexCount;
			vertexCount = 0;
			indiceCount = 0;
			totalTriangleCount += primitiveCount;
			primitiveCount = 0;
		}

		public void DrawPrimitive(PEPrimitive prim)
		{
			if (prim.BorderColor.A == 255 && prim.FillColor.A == 255)
				opaquePrimitives.Add(prim);
			else
				transparentPrimitives.Add(prim);
		}
	}
}